package com.dzhyun.sdk;


/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

import java.io.IOException;

import com.dzhyun.sdk.Pb2Json;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.modules.core.DeviceEventManagerModule;

import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.ws.WebSocket;
import com.squareup.okhttp.ws.WebSocketCall;
import com.squareup.okhttp.ws.WebSocketListener;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import okio.Buffer;
import okio.BufferedSource;

public class DzhChannel extends ReactContextBaseJavaModule {

    private Map<Integer, WebSocket> mWebSocketConnections = new HashMap<>();
    private ReactContext mReactContext;

    public DzhChannel(ReactApplicationContext context) {
        super(context);
        mReactContext = context;
    }

    private void sendEvent(String eventName, WritableMap params) {
        mReactContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName, params);
    }

    @Override
    public String getName() {
        return "DzhChannelModule";
    }

    @ReactMethod
    public void connect(final String url, final int id) {
        OkHttpClient client = new OkHttpClient();

        client.setConnectTimeout(10, TimeUnit.SECONDS);
        client.setWriteTimeout(10, TimeUnit.SECONDS);
        // Disable timeouts for read
        client.setReadTimeout(0, TimeUnit.MINUTES);

        Request request = new Request.Builder()
                .tag(id)
                .url(url)
                .build();

        WebSocketCall.create(client, request).enqueue(new WebSocketListener() {

            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                mWebSocketConnections.put(id, webSocket);
                WritableMap params = Arguments.createMap();
                params.putInt("id", id);
                sendEvent("dzhChannelOpen", params);
            }

            @Override
            public void onClose(int code, String reason) {
                WritableMap params = Arguments.createMap();
                params.putInt("id", id);
                params.putInt("code", code);
                params.putString("reason", reason);
                sendEvent("dzhChannelClosed", params);
            }

            @Override
            public void onFailure(IOException e, Response response) {
                notifyWebSocketFailed(id, e.getMessage());
            }

            @Override
            public void onPong(Buffer buffer) {
            }

            @Override
            public void onMessage(BufferedSource bufferedSource, WebSocket.PayloadType payloadType) {
                String message;
                if (payloadType == WebSocket.PayloadType.BINARY) {

                    try {
                        message = Pb2Json.toJson(bufferedSource.readByteArray());
                        bufferedSource.close();
                    } catch (IOException e) {
                        FLog.e(
                                ReactConstants.TAG,
                                "decode pb failed " + id,
                                e);
                        return;
                    }

                }else{
                    try {
                        message = bufferedSource.readUtf8();
                    } catch (IOException e) {
                        notifyWebSocketFailed(id, e.getMessage());
                        return;
                    }
                    try {
                        bufferedSource.close();
                    } catch (IOException e) {
                        FLog.e(
                                ReactConstants.TAG,
                                "Could not close BufferedSource for WebSocket id " + id,
                                e);
                    }

                }

                WritableMap params = Arguments.createMap();
                params.putInt("id", id);
                params.putString("data", message);
                sendEvent("dzhChannelMessage", params);
            }
        });

        // Trigger shutdown of the dispatcher's executor so this process can exit cleanly
        client.getDispatcher().getExecutorService().shutdown();
    }

    @ReactMethod
    public void close(int code, String reason, int id) {
        WebSocket client = mWebSocketConnections.get(id);
        if (client == null) {
            // This is a programmer error
            throw new RuntimeException("Cannot close WebSocket. Unknown WebSocket id " + id);
        }
        try {
            client.close(code, reason);
            mWebSocketConnections.remove(id);
        } catch (Exception e) {
            FLog.e(
                    ReactConstants.TAG,
                    "Could not close WebSocket connection for id " + id,
                    e);
        }
    }

    @ReactMethod
    public void send(String message, int id) {
        WebSocket client = mWebSocketConnections.get(id);
        if (client == null) {
            // This is a programmer error
            throw new RuntimeException("Cannot send a message. Unknown WebSocket id " + id);
        }
        try {
            client.sendMessage(
                    WebSocket.PayloadType.TEXT,
                    new Buffer().writeUtf8(message));
        } catch (IOException e) {
            notifyWebSocketFailed(id, e.getMessage());
        }
    }

    private void notifyWebSocketFailed(int id, String message) {
        WritableMap params = Arguments.createMap();
        params.putInt("id", id);
        params.putString("message", message);
        sendEvent("dzhChannelFailed", params);
    }
}
